package com.fading.mall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.fading.mall.product.service.CategoryBrandRelationService;
import com.fading.mall.product.vo.Catelog2Vo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fading.common.utils.PageUtils;
import com.fading.common.utils.Query;

import com.fading.mall.product.dao.CategoryDao;
import com.fading.mall.product.entity.CategoryEntity;
import com.fading.mall.product.service.CategoryService;
import org.springframework.util.StringUtils;

@Slf4j
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    StringRedisTemplate redisTemplate;


    @Autowired
    RedissonClient redissonClient;

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        List<CategoryEntity> entityList = baseMapper.selectList(null);
        List<CategoryEntity> tree = entityList.stream().filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(next -> {
                    next.setChildren(getChilderns(next, entityList));
                    return next;
                }).sorted((s1, s2) -> (s1.getSort() == null ? 0 : s1.getSort()) - (s2.getSort() == null ? 0 : s2.getSort()))
                .collect(Collectors.toList());
        return tree;
    }

    @Override
    public void removeMenusByids(List<Long> asList) {
        //TODO 检查被删除对象是否在其他地方被引用

        baseMapper.deleteBatchIds(asList);
    }

    @Override
    @Cacheable({"category"})
    public List<CategoryEntity> getLevel1Categorys() {
        return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
    }

    //修改
    @Override
    public void updateCascade(CategoryEntity category) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("catalogJson-lock");
        //创建写锁
        RLock rLock = readWriteLock.writeLock();

        try {
            rLock.lock();
            this.baseMapper.updateById(category);
            categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }

        //同时修改缓存中的数据
        //删除缓存,等待下一次主动查询进行更新
    }


    //缓存雪崩 ： 同时多个key一起过期，同时大并发都来分开查询这些数据，所有数据都不存在，都去查询数据库
    //解决办法： 将过期时间设置随机值，不要同时过期，减少同时过期的几率

    //缓存击穿： 高频热点key过期，同时大并发查询，没有命中都去查询数据库
    //将热点数据进行加锁，只允许一个查询，

    //缓存穿透 ： 大并发查询一个永不存在的数据，没有缓存，同时都去查询数据库。造成数据崩溃
    //解决办法： 将空值也进行缓存

    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        Map<String, List<Catelog2Vo>> jsonFromDb = null;
        if (StringUtils.isEmpty(catalogJson)) {
            return getCatalogJsonWithRedisLock();
        } else {
            log.debug("缓存命中");
            Map<String, List<Catelog2Vo>> res = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
            return res;
        }
    }


    public Map<String, List<Catelog2Vo>> getCatalogJsonWithRedisson() {
        RLock lock = redissonClient.getLock("catalogJson-lock");
        lock.lock();
        try {
            return getCatalogJsonFromDbAndSetRedis();
        } finally {
            lock.unlock();
        }
    }


    public Map<String, List<Catelog2Vo>> getCatalogJsonWithRedisLock() {
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        Map<String, List<Catelog2Vo>> resMap = null;

        while (!lock) {
            uuid = UUID.randomUUID().toString();
            lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        }
        log.debug("获取锁成功，执行数据库查询");
        try {
            resMap = getCatalogJsonFromDbAndSetRedis();
        } finally {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
        }
        return resMap;
    }


    public Map<String, List<Catelog2Vo>> getCatalogJsonWithLocalLock() {
        synchronized (this) {
            return getCatalogJsonFromDbAndSetRedis();
        }
    }


    //查询数据库并将数据重新放入缓存
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbAndSetRedis() {

        //获取锁先查询一次缓存，在确定是否需要查询，防止等待过程中其他线程已经放入缓存中
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        if (!StringUtils.isEmpty(catalogJson)) {
            log.debug("检查缓存是否更新，缓存已更新直接返回");
            return JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
        }

        log.debug("缓存没有数据直接查询数据库");
        //将数据库的多次查询变为一次
        List<CategoryEntity> selectList = this.baseMapper.selectList(null);

        //1、查出所有分类
        //1、1）查出所有一级分类
        List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);

        //封装数据
        Map<String, List<Catelog2Vo>> parentCid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //1、每一个的一级分类,查到这个一级分类的二级分类
            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());

            //2、封装上面的结果
            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntities != null) {
                catelog2Vos = categoryEntities.stream().map(l2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName().toString());

                    //1、找当前二级分类的三级分类封装成vo
                    List<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());

                    if (level3Catelog != null) {
                        List<Catelog2Vo.Category3Vo> category3Vos = level3Catelog.stream().map(l3 -> {
                            //2、封装成指定格式
                            Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());

                            return category3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(category3Vos);
                    }

                    return catelog2Vo;
                }).collect(Collectors.toList());
            }

            return catelog2Vos;
        }));


        String jsonString = JSON.toJSONString(parentCid);
        log.debug("查询数据库并将结果放入redis");
        redisTemplate.opsForValue().set("catalogJson", jsonString, 1, TimeUnit.DAYS);
        return parentCid;

    }

    private List<CategoryEntity> getParent_cid(List<CategoryEntity> selectList, Long parentCid) {
        List<CategoryEntity> categoryEntities = selectList.stream().filter(item -> item.getParentCid().equals(parentCid)).collect(Collectors.toList());
        return categoryEntities;
        // return this.baseMapper.selectList(
        //         new QueryWrapper<CategoryEntity>().eq("parent_cid", parentCid));
    }


    private List<CategoryEntity> getChilderns(CategoryEntity parent, List<CategoryEntity> all) {
        return all.stream().filter(categoryEntity -> categoryEntity.getParentCid().equals(parent.getCatId()))
                .map(categoryEntity -> {
                    categoryEntity.setChildren(getChilderns(categoryEntity, all));
                    return categoryEntity;
                }).sorted((s1, s2) -> (s1.getSort() == null ? 0 : s1.getSort()) - (s2.getSort() == null ? 0 : s2.getSort()))
                .collect(Collectors.toList());
    }

}